﻿define([
    'Wjs/core/typeBuilder'
],
function (typeBuilder) {
    'use strict';

    var escape;

    return typeBuilder.defineOnce({

        type: 'Template',

        internal: {

            onBeforeTypeDefined: function () {

                escape = function (c) {
                    switch (c) {
                        case '\"': return '\\\"';
                        case '\n': return '\\n';
                        case '\u2028': return '\\u2028';
                        case '\u2029': return '\\u2029';
                        default: return c;
                    }
                };
            },

            construct: function (cfg) {

                cfg = cfg || {};

                // Provide the latest standard (ES6) placeholders
                this.startDelimiter = cfg.start || '${';
                this.endDelimiter = cfg.end || '}';
            }

        },

        // If the string s at starting position i has a template starting delimiter then returns the position (zero indexed) after that delimiter, otherwise returns -1
        getPositionAfterStartDelimiter: function (s, i) {
            var startDelimiter = this.startDelimiter,
                length,
                j;

            if (s[i] === startDelimiter[0]) { // Possible match

                length = startDelimiter.length;

                for (++i, j = 1; j < length; ++i, ++j) { // Start from the next index

                    if (s[i] != startDelimiter[j]) {
                        return -1;
                    }
                }

                return i;
            }

            return -1; // No starting match
        },

        // If the string s at starting position i has a template ending delimiter then returns the position (zero indexed) after that delimiter, otherwise returns -1
        getPositionAfterEndDelimiter: function (s, i) {
            var endDelimiter = this.endDelimiter,
                length,
                j;

            if (s[i] === endDelimiter[0]) { // Possible match

                length = endDelimiter.length;

                for (++i, j = 1; j < length; ++i, ++j) { // Start from the next index

                    if (s[i] != endDelimiter[j]) {
                        return -1;
                    }
                }

                return i;
            }

            return -1; // No ending match
        },

        // Creates a function to quickly concatenate the literals with the values
        compile: function (s) {
            var me = this,
                length = s.length,
                buffer = '',
                body = '',
                isLiteral = true,
                i = 0,
                delimiterStartingPosition,
                delimiterEndingPosition;

            if (!s) { // Return an empty string function
                return new Function('o', 'return "";');
            }

            if (Wjs.isFunction(s)) {
                return s; // Already compiled
            }

            if (!Wjs.isString(s)) {
                throw new TypeError('Template source must be a string');
            }

            while (i < length) {

                if (isLiteral) {

                    delimiterStartingPosition = this.getPositionAfterStartDelimiter(s, i);

                    if (delimiterStartingPosition > -1) { // Start delimiter was found

                        i = delimiterStartingPosition; // Update the position

                        if (buffer.length > 0) {
                            body += '"' + buffer + '"';
                            buffer = '';
                        }

                        isLiteral = false;

                        if (body.length > 0 && i < length - 1) { // The text continues so add the plus for the next chunk
                            body += ' + ';
                        }
                    }
                    else { // Not start delimiter
                        buffer += escape(s[i++]);
                    }
                }
                else { // Template parameter

                    delimiterEndingPosition = this.getPositionAfterEndDelimiter(s, i);

                    if (delimiterEndingPosition > -1) {

                        i = delimiterEndingPosition;

                        if (buffer.length > 0) {

                            if (buffer.indexOf('.') > -1) {

                                buffer = buffer.replace(/\./g, '"]["');
                            }

                            body += '(typeof o["' + buffer + '"] === "function" ? o["' + buffer + '"]() : o["' + buffer + '"])';
                            buffer = '';
                        }

                        isLiteral = true;

                        if (body.length > 0 && i < length) { // The text continues so add the plus for the next chunk
                            body += ' + ';
                        }
                    }
                    else { // Not start delimiter
                        buffer += escape(s[i++]);
                    }
                }
            }

            // Add the remaining content to the body
            if (buffer.length > 0) {
                if (isLiteral) {
                    body += '"' + buffer + '"';
                }
                else {
                    body += '(typeof o["' + buffer + '"] === "function" ? o["' + buffer + '"]() : o["' + buffer + '"])';
                }
            }

            return new Function('o', 'return ' + body + ';');
        }

    });

});
